import pandas as pd

from src.laptop.model.laptop_model_config import MODEL_FEATURES, LAPTOP_LEVEL_ID_MAPPING, \
    LAPTOP_COLUMNS_MAPPING, LEVEL_BIN,MODEL_FEATURES_NEW,DF_FEATURE_CLUMNS_MAPPING,BRAND_DISCONT_API_MAPPING,ALL_FEATURES,LEVEL_TEMPLATE_MAPPING,LAPTOP_LEVEL_ID_MAPPING_DF
from src.laptop.preprocessor import prop_preprocessor as prop
from src.utils.config import config, logger
from src.utils.model_utils import cal_period, load_sku_property,load_sku_property_gc
import time
import scipy.stats as stats
from src.laptop.model import laptop_model_config
import re
import numpy


# 笔记本SKU属性在redis中key的前缀
LAPTOP_REDIS_PREFIX_KEY = config.get_config('project', 'laptop_redis_prefix_key')

def random_ratio(num):
  # logger.info('随机生成折扣')
  lower, upper = -0.01, 0.01
  mu, sigma = 0.0, 0.005
  X = stats.truncnorm((lower - mu) / sigma, (upper - mu) / sigma, loc=mu, scale=sigma)
  samples = X.rvs(num)
  samples = samples + 1
  return samples

def preprocess_delete_column_to_train_data(data):
    model_data = data
    t1 = time.time()
    # model_data[['product_id','brand_id','level_id']].fillna(-1, inplace=True)
    model_data.fillna('unknown',inplace=True)

    delete_column_product_keys = laptop_model_config.GET_DELETE_COLUMN_PRODUCT_ID
    groups=delete_column_product_keys.groupby(['column_name','default_value'])['product_key']
    for name, group in groups:
        model_data.loc[model_data.product_id.isin(group), name[0]] = name[1]
        logger.info('preprocess delete column={},default_value={},product_key size={}'.format(name[0],name[1],len(group)))

    logger.info("preprocess_delete_column_to_train_data is done... consume time {} min".format((time.time() - t1) / 60))
    return model_data

def get_ssd_num(x):
    '''
    获取ssd大小 solid_state_disk
    '''
    ssd_numes = []
    if x == 'unknown':
        return 0
    if x == '不含固态硬盘':
        return 0
    ssd_num_gb = re.findall('([1-9]\d*\.?\d*G)', x)
    ssd_num_tb = re.findall('([1-9]\d*\.?\d*T)', x)
    ssd_num_tb = [str(float(t[:-1]) * 1000) + 'G' for t in ssd_num_tb]
    ssd_numes.extend(ssd_num_gb)
    ssd_numes.extend(ssd_num_tb)
    if '-' not in x:
        ssd_num = float(ssd_numes[0][:-1])
    else:
        ssd_numes = [float(i[:-1]) for i in ssd_numes]
        ssd_num = numpy.mean(ssd_numes)

    return ssd_num

def get_hard_disk_num(x):
    '''
    获取机械硬盘大小 hard_disk
    '''
    hd_numes = []

    # 处理方法有待讨论
    if x == 'unknown':
        '''
        占比 16.6%
        可以根据是否包含固态硬盘等特征进行细分
        应该返回0，经过观察，unknown的高价型号多为全ssd，低端型号为0
        '''
        # return 1400
        return 0

    if x =='不含机械硬盘':
        '''
        占比 45.6%
        应该返回0，因为 同样的机器 ssd 500g,一个有hhd一个没有，设置为1700对有hhd的机器不公平
        '''
        # return 1700
        return 0

    hd_num_gb = re.findall('([1-9]\d*\.?\d*G)', x)
    hd_num_tb = re.findall('([1-9]\d*\.?\d*T)', x)
    hd_num_tb = [str(float(t[:-1]) * 1000) + 'G' for t in hd_num_tb]
    hd_numes.extend(hd_num_gb)
    hd_numes.extend(hd_num_tb)

    if '-' not in x:
        hd_num = float(hd_numes[0][:-1])
    else:
        hd_numes = [float(i[:-1]) for i in hd_numes]
        hd_num = numpy.mean(hd_numes)

    return hd_num

def get_memory_num(x, brand_id):
    '''
    获取内存大小 memory
    '''
    # memory_num = 0
    if x == '无内存':
        memory_num = 0

    elif x == 'unknown':
        '''
        大部分型号为低价机器
        微软高端机拉升了售价
        powered_on
        '''
        if brand_id == 272:
            memory_num = 8
        else:
            memory_num = 0

    elif x == 'MS_SSD':
        if brand_id == 272:
            memory_num = 8
    else:
        # print(x)
        memory_numes = re.findall('([1-9]\d*\.?\d*G)', x)
        if '-' not in x:
            # print(memory_numes)
            memory_num = float(memory_numes[0][:-1])
        else:
            memory_numes = [float(i[:-1]) for i in memory_numes]
            memory_num = numpy.mean(memory_numes)
    # print(x, ' ', memory_num)
    return memory_num

def apple_laptop_year(product_name, brand_id):
    '''
    苹果电脑笔记本年份
    '''
    try:
        if brand_id != 52:
            return 'other_brand'

        year_lis = re.findall('(\d+)年', product_name)
        if len(year_lis) > 0:
            return str(int(year_lis[0]))
        else:
            return 'no_year'
    except Exception as e:
        logger.info('product_name is {0} brand_id is {1}'.format(product_name, str(brand_id)))

        raise e

def apple_laptop_size(product_name, brand_id):
    '''
    苹果电脑笔记本尺寸
    '''
    if brand_id != 52:
        return 'other_brand'

    size_lis = re.findall('(\d+)寸', product_name)
    if len(size_lis) > 0:
        return str(int(size_lis[0]))
    else:
        return 'no_size'

def apple_laptop_is_pro(product_name, brand_id):
    '''
    苹果电脑笔记本
    区分 pro air other
    '''
    if brand_id != 52:
        return 'other_brand'
    product_name = product_name.lower()
    if 'pro' in product_name:
        return 'pro'
    if 'air' in product_name:
        return 'air'
    return 'other'

def get_guarantee_index_dic():
    '''
    保修情况
    '''
    guarantee_index_dic = {'unknown': 1, '保修1个月内或过保': 2, '无法验证保修情况': 3, '保修1个月以上': 4, '未激活': 5}
    return guarantee_index_dic

def get_shop_num(data):
    '''
    获取每个product_id的销量
    获取物品销量特征
    '''
    shop_num_data = data[['product_id']].groupby(['product_id']).size()
    shop_num_data_dic = shop_num_data.to_dict()
    return shop_num_data_dic


def get_features(data, is_cal_period):
    '''
    特征提取
    '''

    # kr period转化为数字
    data['period_num'] = data['period'].astype(int)


    # 连续型特征
    data['ssd_num'] = data['ssd'].apply(lambda x: get_ssd_num(x))
    data['hd_num'] = data['hdd'].apply(lambda x: get_hard_disk_num(x))
    data['memory_num'] = data.apply(lambda x: get_memory_num(x['memory'], x['product_brand_id']), axis=1)

    data['product_id_num'] = data['product_id'].apply(lambda x: float(x))

    # 有大小顺序的特征 在神经网络中看成连续型特征
    guarantee_index_dic = get_guarantee_index_dic()
    data['guarantee_class'] = data['guarantee'].apply(lambda x: guarantee_index_dic.get(x, 1))


    # 苹果笔记本特征
    data['apple_year'] = data.apply(lambda x: apple_laptop_year(x['product_name'], x['product_brand_id']), axis=1)
    data['apple_size'] = data.apply(lambda x: apple_laptop_size(x['product_name'], x['product_brand_id']), axis=1)
    data['apple_is_pro'] = data.apply(lambda x: apple_laptop_is_pro(x['product_name'], x['product_brand_id']), axis=1)

    return data

def high_property_to_low_property(property_name, property_cla, property_con):
    change_property_flag = 0
    if property_name=='ssd':
        if property_con>3000:
            property_cla = '固态硬盘3TB'
            property_con = 3000
            change_property_flag=1
    elif property_name=='memory':
        if property_con>32:
            property_cla = '32G'
            property_con = 32
            change_property_flag = 1
    elif property_name=='hdd':
        if property_con>2000:
            property_cla = '机械硬盘2TB'
            property_con = 2000
            change_property_flag = 1
    return property_cla, property_con, change_property_flag


def preprocess_model_data(data, is_cal_period=True):
    """
    预处理成模型数据
    :param data: 原始数据
    :param is_cal_period: 是否计算周期
    :return:
    """
    logger.info('preprocess_model_data is doing ...')
    model_data = data.rename(columns=LAPTOP_COLUMNS_MAPPING)
    # model_data['memory'].replace(prop.memory_mapping, inplace=True)
    # model_data['ssd'].replace(prop.ssd_mapping, inplace=True) #使用kr特征需要注销
    # model_data['hdd'].replace(prop.hhd_mapping, inplace=True) #使用kr特征需要注销
    model_data['CPU']=model_data['CPU'].astype(str)
    model_data['GPU']=model_data['GPU'].astype(str)
    model_data['CPU'] = model_data.CPU.astype(str).map(lambda x: prop.rename_cpu(x))
    model_data['GPU'] = model_data.GPU.astype(str).map(lambda x: prop.rename_gpu(x))

    if is_cal_period:
        model_data['period'] = model_data['settle_list_create_date'].apply(cal_period, interval=14).astype(str)
    else:
        model_data['period']='0'

    # kr 提取连续型特征
    model_data = get_features(model_data, is_cal_period)

    model_data['ssd'], model_data['ssd_num'], model_data['ssd_flag'] = zip(*model_data.apply(lambda x: high_property_to_low_property('ssd', x['ssd'], x['ssd_num']), axis=1))
    model_data['memory'], model_data['memory_num'], model_data['memory_flag'] = zip(*model_data.apply(lambda x: high_property_to_low_property('memory', x['memory'], x['memory_num']),axis=1))
    model_data['hdd'], model_data['hd_num'], model_data['hd_flag'] = zip(*model_data.apply(lambda x: high_property_to_low_property('hdd', x['hdd'], x['hd_num']), axis=1))

    # 将苹果和微软的hdd标记为特殊字符
    model_data.loc[(model_data['hdd'].isin(['unknown', '不含机械硬盘'])) &
                   (model_data['product_brand_name'] == '苹果'), 'hdd'] = 'APPLE_HDD'
    model_data.loc[(model_data['hdd'].isin(['unknown', '不含机械硬盘'])) &
                   (model_data['product_brand_name'] == '微软'), 'hdd'] = 'MS_HDD'

    try:
        for name, group in laptop_model_config.ADD_COLUMN_GROUPS:
            model_data.loc[model_data.product_id.isin(group), name[0]] = name[1]
            logger.info(
                'preprocess delete column={},default_value={},product_key size={}'.format(name[0], name[1], len(group)))

    except Exception as e:
        logger.critical('skulevel笔记本模型属性容忍失败：@{}'.format(e))

    # 等级封箱
    # model_data['level_bin'] = model_data['level_id'].astype(str)

    logger.info('preprocess_model_data is doine')
    return model_data


def process_brand_discont_mapping(data):

    t1 = time.time()
    data['discont']=data['brand_id']
    data['discont']=data['discont'].map(BRAND_DISCONT_API_MAPPING)
    data.discont=data.discont.fillna(value=1)
    data['price_tmp']=data['price']
    data['price']=data['price']*data['discont']
    tmp = data[['sku_id','level_id', 'price', 'price_tmp','discont']]
    # logger.info('discont后的价格：{}'.format(tmp))
    logger.info("process_brand_discont_mapping is done... consume time {} min".format((time.time() - t1) / 60))
    return data



def process_predict_data(data, brand_fe, product_fe, median_dict, median_dict_temp, process_level=True):
    """
    预处理传入的预测数据
    :param data: 数据
    :param process_level: 是否处理等级
    :return:
    """
    logger.info('processing predict data')

    # sku_property = load_sku_property(data, LAPTOP_REDIS_PREFIX_KEY, LAPTOP_COLUMNS_MAPPING)
    t0=time.time()
    try:
        ti1 = time.time()
        sku_property = load_sku_property(data, LAPTOP_REDIS_PREFIX_KEY, LAPTOP_COLUMNS_MAPPING)
        logger.info('load_sku_property use time @{}'.format(time.time() - ti1))
    except Exception as e:
        logger.info('load_sku_property timeout@{}'.format(e))
        sku_property=[]
    logger.info('load_sku_property use time@{}1'.format(time.time() - t0))

    if sku_property is None or len(sku_property)==0:
        predict_data = pd.DataFrame()
        return predict_data

    # 检查特征是否完整
    predict_data = pd.DataFrame(sku_property)
    # logger.info('get feature use time-1@{}'.format(time.time() - t0))


    # 检查是否有product_id，如果没有，增加product_id，默认为0
    if 'product_id' not in predict_data:
        predict_data['product_id'] = 0
    if process_level:
        predict_data = predict_data.merge(LAPTOP_LEVEL_ID_MAPPING_DF, how='left', on='level_id') #获取level_name
        predict_data = predict_data.merge(LEVEL_TEMPLATE_MAPPING, how='left', on='product_id') #获取等级模板

    logger.info('get feature use time0@{}'.format(time.time() - t0))

    logger.info('preprocess model data')
    predict_data['product_brand_id'] = predict_data['brand_id']

    for feature in ALL_FEATURES:
        if feature not in predict_data:
            predict_data[feature] = 'unknown'
    predict_data.fillna('unknown', inplace=True)

    #防止接口数据属性中存在换行符等字符
    old_feature = ['hdd','ssd','CPU','GPU','memory','purchase_way','guarantee','product_brand_name']
    for c in predict_data:
        if c in old_feature:
            predict_data[c] = predict_data[c].map(lambda x: x.strip())

    logger.info('get feature use time1@{}'.format(time.time() - t0))

    predict_data = preprocess_model_data(predict_data, is_cal_period=False)
    logger.info('get feature use time2@{}'.format(time.time() - t0))

    predict_data['secondary_level_id'] = predict_data['level_id']

    predict_data = predict_data.drop(['brand_amount','brand_price_kurt','brand_price_mad','brand_price_max','brand_price_mean',
    'brand_price_median','brand_price_min','brand_price_skew','brand_price_std','brand_price_sum',
    'product_amount'], axis=1, inplace=False)

    predict_data = predict_data.merge(brand_fe, how='left', on='product_brand_id')
    predict_data = predict_data.merge(product_fe, how='left', on='product_id')

    logger.info('get feature use time3@{}'.format(time.time() - t0))

    predict_new_brand_id = list(set(predict_data['product_brand_id'])-set(brand_fe['product_brand_id']))
    predict_new_product_id = list(set(predict_data['product_id'])-set(product_fe['product_id']))
    new_brand_id_flag = 0
    new_product_id_flag = 0
    predict_data.loc[predict_data['brand_price_std'].isna(),['brand_price_std']]=0
    # predict_data['brand_price_std'].fillna(0, inplace=True)
    if len(predict_new_brand_id)>0:
        predict_data.loc[predict_data['product_brand_id'].isin(predict_new_brand_id),['brand_amount']] = 1
        predict_data.loc[predict_data['product_brand_id'].isin(predict_new_brand_id),['brand_price_max', 'brand_price_mean', 'brand_price_median', 'brand_price_min']] = 200
        # predict_data.fillna({'brand_price_max': 200, 'brand_price_mean': 200, 'brand_price_median': 200, 'brand_price_min': 200}, inplace=True)
        new_brand_id_flag=1
    if len(predict_new_product_id)>0:
        predict_data.loc[predict_data['product_id'].isin(predict_new_product_id),['product_amount']] = 1
        new_product_id_flag=1

    if new_brand_id_flag:
        predict_data.fillna(median_dict, inplace=True)
    else:
        predict_data.loc[predict_data['brand_price_kurt'].isna(), ['brand_price_kurt']] = median_dict_temp.get('brand_price_kurt')
        predict_data.loc[predict_data['brand_price_skew'].isna(), ['brand_price_skew']] = median_dict_temp.get('brand_price_skew')

        # predict_data.fillna(median_dict_temp, inplace=True)

    # predict_data['brand_amount'].fillna(1, inplace=True)
    # predict_data['brand_price_std'].fillna(0, inplace=True)
    # predict_data[['brand_price_max', 'brand_price_mean', 'brand_price_median', 'brand_price_min']].fillna(200, inplace=True)
    # predict_data['product_amount'].fillna(1, inplace=True)
    # predict_data.fillna(median_dict, inplace=True)

    logger.info('get feature use time4@{}'.format(time.time() - t0))


    # 将缺失值用unknown填补
    # predict_data.fillna('unknown', inplace=True)

    logger.info('process_predict_data is done')
    logger.info('get feature use time5@{}'.format(time.time() - t0))

    return predict_data



def process_predict_data_gc(data, process_level=True):
    """
    预处理传入的预测数据
    :param data: 数据
    :param process_level: 是否处理等级
    :return:
    """
    logger.info('processing predict data')

    # sku_property = load_sku_property(data, LAPTOP_REDIS_PREFIX_KEY, LAPTOP_COLUMNS_MAPPING)
    t0=time.time()
    try:
        ti1 = time.time()
        # res_predict['history_avg_price'] = load_history_avg_price_batch(mobile_model_config.HISTORY_AVG_PRICE_NAME,res_predict['key'].values)
        sku_property = load_sku_property_gc(data, LAPTOP_REDIS_PREFIX_KEY, LAPTOP_COLUMNS_MAPPING)
        logger.info('load_sku_property use time @{}'.format(time.time() - ti1))
    except Exception as e:
        logger.info('load_sku_property timeout@{}'.format(e))
        sku_property=[]
    logger.info('load_sku_property use time@{}1'.format(time.time() - t0))

    if sku_property is None or len(sku_property)==0:
        predict_data = pd.DataFrame()
        return predict_data

    # 检查特征是否完整
    predict_data = pd.DataFrame(sku_property)
    predict_data.rename(columns=DF_FEATURE_CLUMNS_MAPPING,inplace=True)
    for feature in MODEL_FEATURES_NEW:
        if feature not in predict_data:
            predict_data[feature] = 'unknown'
    # 检查是否有product_id，如果没有，增加product_id，默认为0
    if 'product_id' not in predict_data:
        predict_data['product_id'] = 0
    # 将缺失值用unknown填补
    predict_data.fillna('unknown', inplace=True)
    if process_level:
        predict_data['product_level_name'] = predict_data['level_id'].copy()
        predict_data['product_level_name'] = predict_data['product_level_name'].replace(
            LAPTOP_LEVEL_ID_MAPPING).astype(str)
    logger.info('preprocess model data')
    predict_data = preprocess_model_data(predict_data, is_cal_period=False)
    logger.info('process_predict_data is done')
    return predict_data
